module Husky

  #!/usr/bin/env ruby

  require 'yaml'
  require 'xmpp4r'
  require 'xmpp4r/bytestreams'
  require 'xmpp4r/roster'
  require 'xmpp4r/version'

    Jabber::debug = true

  class Transfer
    attr_reader :peer, :filename, :bytes, :filesize

    def initialize(filetransfer, peer, filename, filesize, msgblock)
      @filetransfer = filetransfer
      @peer = peer
      @filename = filename
      @filesize = filesize
      @msgblock = msgblock

      @done = false
      @bytes = 0
      @stats = [0]
      @stats_lock = Mutex.new
      @stats_thread = Thread.new { stats_loop }


      @streamhost_cb = lambda { |streamhost,state,e|
        case state
        when :connecting
          say "Connecting to #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})"
        when :success
          say "Successfully using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port})"
        when :failure
          say "Error using #{streamhost.jid} (#{streamhost.host}:#{streamhost.port}): #{e}"
        end
      }
    end


    

    def done?
      @stats_thread.kill if @done
      @done
    end

    def stats_loop
      loop {
        sleep 1
        @stats_lock.synchronize {
          @stats.push @bytes
          @stats.shift if @stats.size > 5
        }
      }
    end

    def stats
      @stats_lock.synchronize {
        if @stats.first >= @stats.last
          0
        else
          (@stats.last - @stats.first) / (@stats.size - 1)
        end
      }
    end

    def say(text)
      @msgblock.call(text)
    end

    def transfer(from, to)
      while buf = from.read
        to.write buf
        to.flush
        @bytes += buf.size
      end
    end
  end

  class Upload < Transfer
    def initialize(filetransfer, iq, filename, filesize, can_range, msgblock)
      super(filetransfer, iq.from, filename, filesize, msgblock)

      if filename.size < 1
        say "What is this file for?"
        @done = true
        return
      end

      offset = nil
      if File::exist?(filename)
        if File::size(filename) < filesize and can_range
          @bytes = offset = File::size(filename)
          say "Will retrieve #{filename} starting at #{offset}"
        else
          say "#{filename} already exists"
          filetransfer.decline(iq)
          @done = true
          return
        end
      end

      Thread.new {
        begin
          stream = filetransfer.accept(iq, offset)
          if stream.kind_of?(Jabber::Bytestreams::SOCKS5Bytestreams)
            stream.connect_timeout = 5
            stream.add_streamhost_callback(nil, nil, &@streamhost_cb)
          end

          if stream.accept
            outfile = File.new(filename, (offset ? 'a' : 'w'))

            transfer(stream, outfile)

            outfile.close
            stream.close
            @done = true
          else
            say "Stream failed"
            @done = true
          end
        rescue
          puts $!.backtrace.first
          say "Error: #{$!}"
          @done = true
        end
      }
    end
  end

  class Download < Transfer
    def initialize(filetransfer, peer, filename, msgblock)
      begin
        filesize = File.size(filename)
      rescue
        filesize = 0
      end

      super(filetransfer, peer, filename, filesize, msgblock)

      Thread.new {
        begin
          raise "No regular file" unless File.file?(filename)

          source = Jabber::FileTransfer::FileSource.new(filename)
          source.length = filesize
          stream = filetransfer.offer(peer, source)
          unless stream
            raise "Well, you should accept what you request..."
            @done = true
          end

          #        if stream.kind_of?(Jabber::Bytestreams::SOCKS5Bytestreams)
          #          socksconf.call(stream)
          #          stream.add_streamhost_callback(nil, nil, &@streamhost_cb)
          #        end

          puts "ready to open stream"
          p source
          p stream
          stream.open
          transfer(source, stream)
          stream.close
          @done = true
        rescue
          say "Error: #{$!}"
          @done = true
        end
      }
    end
  end

  class FileServe
    def initialize(jid, pass, directory)
      @uploads = 0
      @downloads = 0

      @transfers = []
      @transfers_lock = Mutex.new

      @client = Jabber::Client.new Jabber::JID.new(jid)
      @client.connect
      @client.auth(pass)

      @ft = Jabber::FileTransfer::Helper.new(@client)
#          Jabber::Version::SimpleResponder.new(@client,
      #                                       "XMPP4R FileServe example",
      #                                       Jabber::XMPP4R_VERSION,
      #                                       IO.popen('uname -sr').readlines.to_s.strip)
      @ft.allow_ibb = true
      @ft.allow_bytestreams = false

      register_handlers

      @directory = directory
      @directory.gsub!(/\/+$/, '')

      #    @socksserver = Jabber::Bytestreams::SOCKS5BytestreamsServer.new(conf['socks']['port'])
      #
      #    conf['socks']['addresses'].each { |addr| @socksserver.add_address(addr) }
      #
      #    @proxies = []
      #    conf['socks']['proxies'].collect { |jid|
      #      puts "Querying proxy #{jid}..."
      #      begin
      #        @proxies.push(Jabber::Bytestreams::SOCKS5Bytestreams::query_streamhost(@client, jid))
      #      rescue
      #        puts "Error: #{$!}"
      #      end
      #    }

      Thread.new { presence }
      Thread.new { cleaner }

      # Panic reboot ;-)
      @client.on_exception { initialize(jid, pass, directory) }
    end

    def human_readable(num)
      unit = ''
      units = %w(K M G T P E)
      while num > 10240 and units.size > 0
        num /= 1024
        unit = units.shift
      end
      "#{num} #{unit}B"
    end

    def presence
      rate_upload = 0
      rate_download = 0

      old_status = nil
      loop {
        # Update the @rate_* variables
        @transfers_lock.synchronize {
#          puts "in lock"
          @transfers.each { |t|
            if t.kind_of?(Upload) and t.stats > rate_upload
              rate_upload = t.stats
            elsif t.kind_of?(Download) and t.stats > rate_download
              rate_download = t.stats
            end
          }
#          puts "11"
        }
#        puts "out of lock"
        #      status = "Attempted #{@downloads} downloads (max. #{human_readable rate_download}/s) and #{@uploads} uploads (max. #{human_readable rate_upload}/s)"
        status = "Attempted #{@downloads} downloads (max. #{human_readable rate_download}/s)"
        #        status = "YES"
#        puts "status: #{status}"

        @client.send(Jabber::Presence.new(:chat, status)) if status != old_status
        old_status = status

        sleep 1

      }
    end

    def register_handlers
      @client.add_message_callback { |msg|
        if msg.type == :chat and msg.body and msg.from != 'pentabarf@pentabarf.org/rails'
          puts "<#{msg.from}> #{msg.body.strip}"
          cmd, arg = msg.body.split(/ /, 2)

          command(msg.from, cmd, arg)
        end
      }

      #    @ft.add_incoming_callback { |iq,file|
      #
      #      say = lambda { |text|
      #        say(iq.from, text)
      #      }
      #
      #      puts "Incoming file transfer from #{iq.from}: #{file.fname} (#{file.size / 1024} KB)"
      #      filename = file.fname.split(/\//).last
      #      filename.gsub!(/^\.+/, '')
      #
      #      puts "Range: #{file.range != nil}"
      #      transfer = Upload.new(@ft, iq, "#{@directory}/#{filename}", file.size, file.range != nil, say)
      #      @uploads += 1
      #
      #      @transfers_lock.synchronize {
      #        @transfers.push(transfer)
      #      }
      #
      #    }

      roster = Jabber::Roster::Helper.new(@client)

      roster.add_subscription_request_callback { |item,presence|
        roster.accept_subscription(presence.from)
      }

    end

    def command(from, cmd, arg)
      say = lambda { |text|
        say(from, text)
      }
      #    socksconf = lambda { |stream|
      #      stream.add_streamhost(@socksserver)
      #      @proxies.each { |sh|
      #        stream.add_streamhost(sh)
      #      }
      #    }

      case cmd
      when 'get'
        arg.gsub!(/\//, '')
        arg.gsub!(/^\.+/, '')
        #        transfer = Download.new(@ft, from, "#{@directory}/#{arg}", say, socksconf)
        transfer = Download.new(@ft, from, "#{@directory}\\#{arg}", say)
        @downloads += 1
        @transfers_lock.synchronize {
          @transfers.push(transfer)
        }
      when 'ls'
        text = ""
        Dir.foreach(@directory) { |file|
          next if file =~ /^\./
          path = "#{@directory}/#{file}"
          text += "#{file} (#{human_readable File.size(path)})\n" if File.file?(path)
        }
        say.call(text.strip)
      when 'stat'
        @transfers_lock.synchronize {
          text = "#{@transfers.size} transfers:\n"
          @transfers.each { |t|
            text += "#{t.filename} (#{t.peer}): #{(t.bytes * 100) / t.filesize}% (#{human_readable t.stats}/s)\n"
          }
        }
        say.call(text.strip)
      when 'help'
        say.call "Download a file: get <filename>\nList directory contents: ls\nLook who is currently wasting bandwidth: stat\nUpload a file, simply send this file"
      else
        say.call "Unknown command: #{cmd}, try help"
      end
    end

    def say(to, text)
      puts ">#{to}< #{text.strip}"
      @client.send(Jabber::Message.new(to, text).set_type(:chat))
    end

    def cleaner
      loop {

        @transfers_lock.synchronize {
          @transfers.delete_if { |t| t.done? }
        }

        sleep 1
      }
    end
  end


end

